﻿using System;
using MongoDB;
using System.Text;
using System.Collections.Generic;
using T0yK4T.IO;
using ToyChat.Server;
using ToyChat.Client;
using T0yK4T.Tools;
using System.Diagnostics;
using System.Threading;

namespace ToyChat.BuiltinModules
{
    /// <summary>
    /// This class is -the- core module of ToyChat, it provides basic messaging and a simple permission system
    /// </summary>
    public class Core : ComponentBase, IModule
    {
        private Dictionary<uint, Action<Packet, IUser>> actions = new Dictionary<uint, Action<Packet, IUser>>();

        private Dictionary<int, IUser> discoveredUsers = new Dictionary<int, IUser>();
        private IClient client;
        private IServer server;
        private Dictionary<string, uint> typeMap = new Dictionary<string, uint>();
        private Dictionary<int, RemoteChannel> currentChannels = new Dictionary<int, RemoteChannel>();
        private IPermissionsHandler permsHandler;
        private IUser currentUser;
        private Dictionary<int, UserPermissions> requestedPermissions = new Dictionary<int,UserPermissions>();
        private CorePane corePane;
        private SynchronizationContext context;
        private object p_Lock = new object();

        /// <summary>
        /// Called when the client receives a <see cref="ChannelControlMessage"/>
        /// </summary>
        /// <param name="packet">The packet that contains the <see cref="ChannelControlMessage"/></param>
        /// <param name="sourceUser">The <see cref="IUser"/> that sent the packet</param>
        private void OnChannelControl_Client(Packet packet, IUser sourceUser)
        {
            ChannelControlMessage message = packet.DeserializeData<ChannelControlMessage>();
            if (message != null)
            {
            }
        }

        /// <summary>
        /// Called when the server receives a <see cref="ChannelControlMessage"/>
        /// </summary>
        /// <param name="packet">The packet that contains the <see cref="ChannelControlMessage"/></param>
        /// <param name="sourceUser">The <see cref="IUser"/> that sent the packet</param>
        private void OnChannelControl_Server(Packet packet, IUser sourceUser)
        {
            ChannelControlMessage message = packet.DeserializeData<ChannelControlMessage>();
            if (message != null)
            {
                IChannel channel = null;
                switch (message.Mode)
                {
                    case ControlMode.Create:
                        if (this.server.CheckPermissions(sourceUser, PermissionFlags.CreateChannel))
                        {
                            if (this.server.ChannelHandler.CreateChannel(message.ChannelName, out channel))
                                this.CreateChannel(sourceUser, channel, message);
                            else
                                this.SendBasicMessage("Unable to create channel", sourceUser);
                        }
                        else
                            this.SendBasicMessage("Operation not permitted", sourceUser);
                        break;
                    case ControlMode.Delete:
                        if (this.server.CheckPermissions(sourceUser, PermissionFlags.DeleteChannel))
                        {
                            if (this.server.ChannelHandler.TryGetChannel(message.ChannelName, out channel))
                            {
                                LeavingMessage msg = new LeavingMessage { ChannelID = channel.ChannelID, LeavingReason = "Channel Deleted" };
                                List<IUser> removeUsers = new List<IUser>();
                                Packet pkt = new Packet(this.GetID(TYPE_USERLEFT));
                                pkt.SourceUserID = -1;
                                pkt.ChannelID = channel.ChannelID;

                                foreach (IUser user in channel.Users)
                                {
                                    msg.UserID = user.UserID;
                                    pkt.DestinationUserID = user.UserID;
                                    pkt.SerializeData(msg);
                                    user.Send(pkt);
                                    removeUsers.Add(user);
                                }
                                foreach (IUser user in removeUsers)
                                    channel.RemoveUser(user, "Channel Deleted");
                                removeUsers.Clear();
                                removeUsers = null;
                                this.SendBasicMessage(string.Format("Channel {0} deleted", channel.ChannelName), sourceUser);
                                this.server.ChannelHandler.DeleteChannel(channel);
                                channel = null;
                            }
                            else
                                this.SendBasicMessage(string.Format("Cannot find channel {0}", message.ChannelName), sourceUser);
                        }
                        else
                            this.SendBasicMessage("You are not allowed to delete channels", sourceUser);
                        break;
                    case ControlMode.Modify:
                        if (this.server.ChannelHandler.TryGetChannel(packet.ChannelID, out channel))
                            this.ModChannel(sourceUser, channel, message);
                        else
                            this.SendBasicMessage("Unable to modify the specified channel, not found", sourceUser);
                        break;
                }

                if (channel != null) // No need to do this step if there are no users in the channel
                {
                    this.server.ChannelHandler.UpdateDB(channel);
                    if (!channel.ContainsUser(sourceUser))
                        channel.AddUser(sourceUser);

                    this.SendChannel(channel, sourceUser);
                    packet.ChannelID = channel.ChannelID;
                    packet.DestinationUserID = -1;
                    packet.SourceUserID = -1;
                    packet.TypeID = this.GetID(TYPE_CHANNELCTRL);
                    channel.Send(packet);
                }
            }
        }

        /// <summary>
        /// Finishes creation of the specified channel
        /// </summary>
        /// <param name="sourceUser">The <see cref="IUser"/> that is creating the channel</param>
        /// <param name="channel">The <see cref="IChannel"/> object that needs to be filled out with informations from <paramref name="message"/></param>
        /// <param name="message">The <see cref="ChannelControlMessage"/> that contains informations about the new channel</param>
        private void CreateChannel(IUser sourceUser, IChannel channel, ChannelControlMessage message)
        {
            channel.MotD = message.MotD ?? string.Empty;
            channel.Password = message.Password ?? string.Empty;
            channel.IsHidden = message.IsHidden ?? false;

            //Setting default permissions
            this.PermissionsHandler.StorePermissions(new DefaultChannelPermissions
            {
                ChannelID = channel.ChannelID,
                Permissions = message.DefaultPermissions ?? ChannelPermissionFlags.Speak
            });
            if (message.UserPermissions != null)
            {
                foreach (KeyValuePair<string, ChannelPermissionFlags> kvp in message.UserPermissions)
                    this.StorePermissions(kvp.Key, channel.ChannelID, kvp.Value, sourceUser);
            }
            this.StorePermissions(sourceUser.Username, channel.ChannelID, ChannelPermissionFlags.All, sourceUser);
            this.SendBasicMessage(string.Format("Channel \"{0}\" created succesfully", channel.ChannelName), sourceUser);
        }

        /// <summary>
        /// Attempts to store the specified <see cref="ChannelPermissionFlags"/> for the user with username <paramref name="username"/>
        /// <para/>
        /// on channel with channelID <paramref name="channelID"/>
        /// </summary>
        /// <param name="username">The username of the user to set permissions for</param>
        /// <param name="channelID">The ID of the channel to store permissions for</param>
        /// <param name="flags">The actual <see cref="ChannelPermissionFlags"/> to store</param>
        /// <param name="callbackUser">The user to notify if changes fail</param>
        private void StorePermissions(string username, int channelID, ChannelPermissionFlags flags, IUser callbackUser)
        {
            IUser user = this.FindUser(username);
            if (user != null) // Verifying that user exists
            {
                this.PermissionsHandler.StorePermissions(new UserPermissions
                {
                    Username = user.Username,
                    ChannelID = channelID,
                    Permissions = flags
                });
            }
            else if (callbackUser != null)
                this.SendBasicMessage(string.Format("Unable to set permissions for User \"{0}\", not found", username), callbackUser);
        }

        /// <summary>
        /// Modifies the specified <see cref="IChannel"/> with informations contains in the specified <see cref="ChannelControlMessage"/>
        /// </summary>
        /// <param name="sourceUser">The user that caused this call (Used to check for permissions)</param>
        /// <param name="channel">The channel to modify</param>
        /// <param name="message">The <see cref="ChannelControlMessage"/> to take informations from</param>
        private void ModChannel(IUser sourceUser, IChannel channel, ChannelControlMessage message)
        {
            if (message.ChannelName != null && message.ChannelName != channel.ChannelName &&
                this.PermissionsHandler.CheckPermissions(sourceUser.Username, channel.ChannelID, ChannelPermissionFlags.SetName))
            {
                IChannel dummy;
                if (this.server.ChannelHandler.TryGetChannel(message.ChannelName, out dummy))
                    this.SendBasicMessage(string.Format("Unable to change channel name for channel {0} to {1}, channel exists", channel.ChannelName, message.ChannelName), sourceUser);
                else
                    channel.ChannelName = message.ChannelName;
            }
            else
                this.SendBasicMessage(string.Format("Not setting channel name on channel {0}, not permitted", channel.ChannelName), sourceUser);
            if (message.DefaultPermissions != null &&
                this.PermissionsHandler.CheckPermissions(sourceUser.Username, channel.ChannelID, ChannelPermissionFlags.SetDefaultPermissions))
            {
                this.PermissionsHandler.StorePermissions(new DefaultChannelPermissions{ ChannelID = channel.ChannelID, Permissions = (ChannelPermissionFlags)message.DefaultPermissions });
            }
            else
                this.SendBasicMessage(string.Format("Not setting Default Permissions on channel {0}, not permitted", channel.ChannelName), sourceUser);
            if (message.GroupPermissions != null && message.GroupPermissions.Count > 0 &&
                this.PermissionsHandler.CheckPermissions(sourceUser.Username, channel.ChannelID, ChannelPermissionFlags.SetPermissions))
            {
                // Not yet implemented
            }
            else
                this.SendBasicMessage(string.Format("Not setting Group Permissions on channel {0}, not permitted", channel.ChannelName), sourceUser);
            if (message.IsHidden != null &&
                this.PermissionsHandler.CheckPermissions(sourceUser.Username, channel.ChannelID, ChannelPermissionFlags.SetHidden))
            {
                channel.IsHidden = (bool)message.IsHidden;
            }
            else
                this.SendBasicMessage(string.Format("Not setting Hidden on channel {0}, not permitted", channel.ChannelName), sourceUser);
            if (message.MotD != null &&
                this.PermissionsHandler.CheckPermissions(sourceUser.Username, channel.ChannelID, ChannelPermissionFlags.SetMotD))
            {
                channel.MotD = message.MotD;
            }
            else
                this.SendBasicMessage(string.Format("Not setting MotD on channel {0}, not permitted", channel.ChannelName), sourceUser);
            if (message.Password != null &&
                this.PermissionsHandler.CheckPermissions(sourceUser.Username, channel.ChannelID, ChannelPermissionFlags.SetPassword))
            {
                message.Password = message.Password;
                if (message.Password == string.Empty)
                {
                    if (!this.PermissionsHandler.CheckPermissions(sourceUser.Username, channel.ChannelID, ChannelPermissionFlags.SetNoPassword))
                        this.SendBasicMessage(string.Format("Cannot set \"no password\" on channel {0}, operation not permitted", channel.ChannelName), sourceUser);
                    else
                        channel.Password = string.Empty;
                }
                else
                    channel.Password = message.Password;
            }
            else
                this.SendBasicMessage(string.Format("Not setting Password on channel {0}, not permitted", channel.ChannelName), sourceUser);
            if (message.UserPermissions != null && message.UserPermissions.Count > 0 &&
                this.PermissionsHandler.CheckPermissions(sourceUser.Username, channel.ChannelID, ChannelPermissionFlags.SetPermissions))
            {
                foreach (KeyValuePair<string, ChannelPermissionFlags> kvp in message.UserPermissions)
                    this.StorePermissions(kvp.Key, channel.ChannelID, kvp.Value, sourceUser);
            }
            else
                this.SendBasicMessage(string.Format("Not setting User Permissions on channel {0}, not permitted", channel.ChannelName), sourceUser);
        }

        /// <summary>
        /// Called client-side whenever a <see cref="ChannelList"/> has been received
        /// </summary>
        /// <param name="packet">The packet that contains the <see cref="ChannelList"/></param>
        /// <param name="sourceUser">The user that sent the packet (this is usually null)</param>
        private void OnChannelList_Client(Packet packet, IUser sourceUser)
        {
            ChannelList list = packet.DeserializeData<ChannelList>();
            if (list != null)
                this.OnChannelListUpdated(list);
        }

        /// <summary>
        /// Fires the <see cref="Core.ChannelListUpdated"/> event
        /// </summary>
        /// <param name="list">The new <see cref="ChannelList"/></param>
        private void OnChannelListUpdated(ChannelList list)
        {
            if (this.ChannelListUpdated != null)
                this.ChannelListUpdated.Invoke(list);
        }

        /// <summary>
        /// Requests a new channel list from the server, the updated list can be retrieved via the <see cref="Core.ChannelListUpdated"/> event
        /// </summary>
        public void UpdateChannelList()
        {
            if (this.client != null)
            {
                Packet pkt = new Packet(this.GetID(TYPE_CHANNELLIST));
                pkt.ChannelID = -1;
                pkt.DestinationUserID = -1;
                this.client.Send(pkt);
            }
        }

        /// <summary>
        /// This event is fired whenever a channel list has been received from the server
        /// </summary>
        public event Action<ChannelList> ChannelListUpdated;

        /// <summary>
        /// Called whenever a channel list "request" is received by the server
        /// </summary>
        /// <param name="packet">The packet that contained the request (this packet should be EMPTY!)</param>
        /// <param name="sourceUser">The user that sent the request</param>
        private void OnChannelList_Server(Packet packet, IUser sourceUser)
        {
            base.LogInformation("User {0} requested channel list", sourceUser.Username);
            Stopwatch sw = new Stopwatch();
            sw.Start();
            ChannelList list = new ChannelList();
            List<ChannelDescriptor> descriptors = new List<ChannelDescriptor>();
            foreach (IChannel channel in this.server.ChannelHandler)
            {
                if (!channel.IsHidden)
                    descriptors.Add(new ChannelDescriptor
                    {
                        ChannelName = channel.ChannelName,
                        IsPasswordProtected = !string.IsNullOrEmpty(channel.Password),
                        UserCount = channel.UserCount,
                        ChannelID = channel.ChannelID
                    });
            }
            list.ChannelDescriptors = descriptors.ToArray();
            Packet p = new Packet(this.GetID(TYPE_CHANNELLIST));
            p.ChannelID = -1;
            p.DestinationUserID = sourceUser.UserID;
            p.SourceUserID = -1;
            p.SerializeData(list);
            sourceUser.Send(p);
            sw.Stop();
            base.LogDebug("Built and sent channel list in {0} ms", sw.ElapsedMilliseconds);
        }

        /// <summary>
        /// This method is called client-side whenever a <see cref="ServerInfoMessage"/> has been received
        /// </summary>
        /// <param name="packet">The packet that contains the <see cref="ServerInfoMessage"/></param>
        /// <param name="sourceUser">The user that sent the message (this should always be null)</param>
        private void OnServerInfo_Client(Packet packet, IUser sourceUser)
        {
            ServerInfoMessage msg = packet.DeserializeData<ServerInfoMessage>();
            if (msg != null && this.ServerInfoUpdated != null)
                this.ServerInfoUpdated.Invoke(msg);
        }

        /// <summary>
        /// This method is called server-side whenever a <see cref="JoinMessage"/> has been received
        /// </summary>
        /// <param name="packet">The packet that contains the <see cref="JoinMessage"/></param>
        /// <param name="sourceUser">The user that sent the packet</param>
        private void OnUserJoined_Server(Packet packet, IUser sourceUser)
        {
            JoinMessage message = packet.DeserializeData<JoinMessage>();
            if (message != null)
            {
                IChannel channel = null;
                if (message.ChannelID == null && !this.server.ChannelHandler.TryGetChannel(message.ChannelName, out channel))
                    this.SendBasicMessage("Unknown channel", sourceUser);
                else if (message.ChannelID != null && !this.server.ChannelHandler.TryGetChannel((int)message.ChannelID, out channel))
                    this.SendBasicMessage("Unknown channel", sourceUser);
                if (channel != null)
                {
                    if (!this.PasswordMatches(channel, message.ChannelPassword))
                        this.SendBasicMessage("Invalid Password", sourceUser);
                    else if (!channel.ContainsUser(sourceUser))
                    {
                        channel.AddUser(sourceUser);
                        this.SendChannel(channel, sourceUser);

                        message.User = new RemoteUser(sourceUser);
                        message.ChannelName = null;
                        message.ChannelPassword = null;

                        Packet response = new Packet(this.GetID(TYPE_USERJOIN));
                        response.DestinationUserID = -1;
                        response.SourceUserID = sourceUser.UserID;
                        response.ChannelID = channel.ChannelID;
                        response.SerializeData(message);

                        channel.Send(response);
                    }
                }
            }
        }

        private bool PasswordMatches(IChannel channel, string password)
        {
            bool ret = false;
            ret |= string.IsNullOrEmpty(password) && string.IsNullOrEmpty(channel.Password);
            ret |= channel.Password == password;
            return ret;
        }

        /// <summary>
        /// This method is called client-side when a <see cref="JoinMessage"/> has been received
        /// </summary>
        /// <param name="packet">The packet that contains the <see cref="JoinMessage"/></param>
        /// <param name="sourceUser">The user that sent the packet (usually null)</param>
        private void OnUserJoined_Client(Packet packet, IUser sourceUser)
        {
            JoinMessage message = packet.DeserializeData<JoinMessage>();
            if (message != null)
            {
                if (message.ChannelID == null)
                {
                    base.LogWarning("Received JoinMessage from server, but ChannelID was null falling back to Packet.ChannelID");
                    message.ChannelID = packet.ChannelID;
                }
                RemoteChannel localChannel;
                if (this.currentChannels.TryGetValue((int)message.ChannelID, out localChannel))
                {
                    if (this.discoveredUsers.ContainsKey(message.User.UserID))
                    {
                        IUser oldUser = this.discoveredUsers[message.User.UserID];
                        this.discoveredUsers[message.User.UserID] = message.User;
                        this.OnUserUpdated(oldUser, message.User);
                    }
                    else
                        this.discoveredUsers.Add(message.User.UserID, message.User);
                    if (!localChannel.ContainsUser(sourceUser))
                        localChannel.AddUserLocal(message.User);
                }
            }
        }

        /// <summary>
        /// Creates a new <see cref="RemoteChannel"/> and sends it to the specified <see cref="IUser"/>
        /// </summary>
        /// <param name="channel">The channel to load and send</param>
        /// <param name="destinationUser">The user to send the channel to</param>
        private void SendChannel(IChannel channel, IUser destinationUser)
        {
            RemoteChannel c = new RemoteChannel(channel);
            Packet packet = new Packet(this.GetID(TYPE_CINFOMSG));
            packet.DestinationUserID = destinationUser.UserID;
            packet.SourceUserID = -1;
            packet.ChannelID = c.ChannelID;
            packet.SerializeData(c);
            destinationUser.Send(packet);
        }

        /// <summary>
        /// Called when a <see cref="LeavingMessage"/> has been received on the server
        /// </summary>
        /// <param name="packet">The packet that contains the <see cref="LeavingMessage"/></param>
        /// <param name="sourceUser">The <see cref="IUser"/> that sent the packet</param>
        private void OnUserLeft_Server(Packet packet, IUser sourceUser)
        {
            LeavingMessage message = packet.DeserializeData<LeavingMessage>();
            if (message != null)
            {
                IChannel channel;
                IUser userToRemove = this.GetUser(message.UserID);
                if (this.server.ChannelHandler.TryGetChannel(message.ChannelID, out channel) && channel.ContainsUser(userToRemove))
                {
                    if (sourceUser.UserID != message.UserID)
                    {
                        if (this.permsHandler.CheckPermissions(sourceUser.Username, packet.ChannelID, ChannelPermissionFlags.Kick))
                        {
                            channel.Send(packet);
                            channel.RemoveUser(userToRemove);
                        }
                        else
                            this.SendBasicMessage("You are not allowed to kick users from this channel", sourceUser);
                    }
                    else
                    {
                        channel.Send(packet);
                        channel.RemoveUser(userToRemove);
                    }
                }
            }
        }

        private void SendBasicMessage(string message, IUser destination)
        {
            if (destination == null)
            {
                base.LogWarning("SendBasicMessage called with no destination");
                return;
            }
            Packet response = new Packet(this.GetID(TYPE_MESSAGE));
            response.DestinationUserID = destination.UserID;
            response.SourceUserID = -1;
            response.ChannelID = -1;
            response.SerializeData(new BasicMessage { UTF8Message = message });
            destination.Send(response);
        }

        /// <summary>
        /// Called when a <see cref="LeavingMessage"/> has been received on the client
        /// </summary>
        /// <param name="packet">The packet that contains the <see cref="LeavingMessage"/></param>
        /// <param name="sourceUser">The user that sent the packet</param>
        private void OnUserLeft_Client(Packet packet, IUser sourceUser)
        {
            LeavingMessage message = packet.DeserializeData<LeavingMessage>();
            if (message != null)
            {
                IUser user = this.GetUser(message.UserID);
                if (user != null)
                {
                    RemoteChannel channel;
                    if (this.currentChannels.TryGetValue(message.ChannelID, out channel))
                        channel.RemoveUserLocal(sourceUser, user, message.LeavingReason);
                    if (user.UserID == this.currentUser.UserID) // Cleanup
                        this.currentChannels.Remove(channel.ChannelID);
                }
            }
        }

        /// <summary>
        /// Called when a <see cref="BasicMessage"/> has been received on the server
        /// </summary>
        /// <param name="packet">The packet that contains the <see cref="BasicMessage"/></param>
        /// <param name="sourceUser">The user that sent the message</param>
        private void OnMessage_Server(Packet packet, IUser sourceUser)
        {
            IChannel dst;
            if (this.server.ChannelHandler.TryGetChannel(packet.ChannelID, out dst))
            {
                if (this.PermissionsHandler.CheckPermissions(sourceUser.Username, dst.ChannelID, ChannelPermissionFlags.Speak))
                    dst.Send(packet);
                else
                {
                    Packet pkt = new Packet(this.GetID(TYPE_MESSAGE));
                    pkt.DestinationUserID = sourceUser.UserID;
                    pkt.SourceUserID = -1;
                    pkt.ChannelID = dst.ChannelID;
                    BasicMessage msg = new BasicMessage { UTF8Message = "You are not allowed to speak here" };
                    pkt.SerializeData(msg);
                    sourceUser.Send(pkt);
                }
            }
            else
            {
                BasicMessage message = packet.DeserializeData<BasicMessage>();
                if (message != null)
                    base.LogWarning("User {0} sent message {1} to an unknown channel (ID: {2})", sourceUser.Username, message.UTF8Message, packet.ChannelID);
                else
                    base.LogWarning("User {0} sent message to an unknown channel (ID: {1}) Message was not deserializable!", sourceUser.Username, packet.ChannelID);
            }
        }

        /// <summary>
        /// Called when a <see cref="BasicMessage"/> has been received on the client
        /// </summary>
        /// <param name="packet">The packet that contains the <see cref="BasicMessage"/></param>
        /// <param name="sourceUser">The user that sent the message</param>
        private void OnMessage_Client(Packet packet, IUser sourceUser)
        {
            BasicMessage message = packet.DeserializeData<BasicMessage>();
            if (message != null)
            {
                RemoteChannel channel;
                if (this.currentChannels.TryGetValue(packet.ChannelID, out channel))
                    channel.OnMessage(message, sourceUser);
                else
                    this.corePane.OnServerMessage(message);
            }
        }

        /// <summary>
        /// Called whenever a <see cref="UserDisconnectedMessage"/> "message" has been received
        /// </summary>
        /// <param name="packet">The packet that contains the message</param>
        /// <param name="sourceUser">The user that sent the message (should be null)</param>
        private void OnUserDisconnected_Client(Packet packet, IUser sourceUser)
        {
            UserDisconnectedMessage message = packet.DeserializeData<UserDisconnectedMessage>();
            if (message != null)
            {
                if (this.discoveredUsers.ContainsKey(message.UserID))
                {
                    IUser user = this.discoveredUsers[message.UserID];
                    user.Disconnect();
                    this.discoveredUsers.Remove(message.UserID);
                    foreach (RemoteChannel c in this.currentChannels.Values)
                        c.RemoveUserLocal(sourceUser, user, "Disconnected");
                    this.OnUserUpdated(user, null);
                }
            }
        }

        /// <summary>
        /// Called client-side when a channel info message has been received
        /// </summary>
        /// <param name="packet">The packet that contains the channel informations</param>
        /// <param name="sourceUser">The <see cref="IUser"/> that sent the packet (usually null, as this message is sent directly by the server)</param>
        private void OnChannelInfo_Client(Packet packet, IUser sourceUser)
        {
            RemoteChannel channel = packet.DeserializeData<RemoteChannel>();
            if (channel != null)
            {
                RemoteChannel localChannel;
                if (this.currentChannels.TryGetValue(channel.ChannelID, out localChannel))
                    localChannel.Reload(channel);
                else
                {
                    foreach (IUser user in channel.Users)
                    {
                        if (!this.discoveredUsers.ContainsKey(user.UserID))
                            this.discoveredUsers.Add(user.UserID, user);
                        else
                        {
                            IUser oldUser = this.discoveredUsers[user.UserID];
                            this.discoveredUsers[user.UserID] = user; // We're assuming that this is the latest version of the user object
                            this.OnUserUpdated(oldUser, user);
                        }
                    }
                    channel.Core = this;
                    this.currentChannels.Add(channel.ChannelID, channel);
                    if (this.context != null)
                    {
                        this.context.Post(new SendOrPostCallback((o) =>
                        {
                            RemoteChannel c = (RemoteChannel)o;
                            ChannelPane pane = new ChannelPane(c, this);
                            this.client.UI.RegisterUIPane(pane);
                        }), channel);
                    }
                }
            }
        }

        /// <summary>
        /// Called when a user has been updated with a more recent version
        /// </summary>
        /// <param name="oldUser">The old <see cref="IUser"/> object</param>
        /// <param name="newUser">The new <see cref="IUser"/> object that replaces the old one</param>
        private void OnUserUpdated(IUser oldUser, IUser newUser)
        {
            if (this.UserUpdated != null)
                this.UserUpdated.Invoke(oldUser, newUser);
        }
        
        /// <summary>
        /// This event is fired whenever a user object has been replaced by a newer version
        /// </summary>
        public event UserUpdatedHandler UserUpdated;

        /// <summary>
        /// This event is fired whenever the client has received a new <see cref="ServerInfoMessage"/> from the server
        /// </summary>
        public event Action<ServerInfoMessage> ServerInfoUpdated;

        /// <summary>
        /// This method is called when a packet containing a <see cref="GetPermissionsMessage"/> is received
        /// </summary>
        /// <param name="packet">The packet that contains the <see cref="GetPermissionsMessage"/></param>
        /// <param name="sourceUser">The user that sent the message</param>
        private void OnGetPerms_Server(Packet packet, IUser sourceUser)
        {
            GetPermissionsMessage msg = packet.DeserializeData<GetPermissionsMessage>();
            if (msg != null && sourceUser != null)
            {
                UserPermissions perms = this.permsHandler.GetPermissions(sourceUser.Username, msg.ChannelID);
                perms.RequestID = msg.RequestID;
                Packet response = new Packet(this.GetID(TYPE_USERPERMS));
                response.SourceUserID = -1;
                response.DestinationUserID = sourceUser.UserID;
                response.ChannelID = perms.ChannelID;
                response.SerializeData(perms);
                sourceUser.Send(response);
            }
        }

        /// <summary>
        /// Called when a <see cref="UserPermissions"/> message is received
        /// </summary>
        /// <param name="packet">The packet that contains the <see cref="UserPermissions"/></param>
        /// <param name="sourceUser">The source user that sent the mssage (this should be null in this case, as this message is sent by the module on the server, not a user)</param>
        private void OnUserPermissions_Client(Packet packet, IUser sourceUser)
        {
            UserPermissions perms = packet.DeserializeData<UserPermissions>();
            if (this.requestedPermissions.ContainsKey(perms.RequestID))
                this.requestedPermissions[perms.RequestID] = perms;
        }

        /// <summary>
        /// Gets a reference to the <see cref="IPermissionsHandler"/> that this module has instantiated
        /// </summary>
        public IPermissionsHandler PermissionsHandler
        {
            get { return this.permsHandler; }
        }

        /// <summary>
        /// Gets the packet TypeID for the specified Type (if it exists)
        /// <para/>
        /// Will return 0 if it does not exist
        /// </summary>
        /// <param name="type">The type to get the packet TypeID for</param>
        /// <returns>The ID of the specified type if it exists, otherwise 0</returns>
        public uint GetID(string type)
        {
            uint id = 0;
            this.typeMap.TryGetValue(type, out id);
            return id;
        }

        /// <summary>
        /// Called from the client when it first loads this module
        /// </summary>
        /// <param name="component"></param>
        public void Initialize(IClient component)
        {
            this.client = component;
            base.Logger = this.client.Logger;
            this.client.Connected += new ClientConnectedHandler(client_Connected);
            this.corePane = new CorePane(this);
            this.client.UI.RegisterUIPane(this.corePane);
            this.context = SynchronizationContext.Current ?? new SynchronizationContext();
            this.permsHandler = new ClientPermissionsHandler(this);
        }

        void client_Connected(IClient sender)
        {
            this.JoinChannel(1, string.Empty);
        }

        /// <summary>
        /// Attempts to join the channel with the specified ID using the specfied password
        /// </summary>
        /// <param name="channelID">The ID of the channel to join</param>
        /// <param name="password">The password of the channel</param>
        public void JoinChannel(int channelID, string password)
        {
            JoinMessage message = new JoinMessage { ChannelID = channelID, ChannelPassword = password };
            Packet pkt = new Packet(this.GetID(TYPE_USERJOIN));
            pkt.ChannelID = -1;
            pkt.DestinationUserID = -1;
            pkt.SerializeData(message);
            if (this.client != null)
                this.client.Send(pkt);
        }

        /// <summary>
        /// Attempts to join the channel with the specified name using the specified password
        /// </summary>
        /// <param name="channelName">The name of the channel to join</param>
        /// <param name="password">The password to use</param>
        public void JoinChannel(string channelName, string password)
        {
            JoinMessage message = new JoinMessage { ChannelName = channelName, ChannelPassword = password };
            Packet pkt = new Packet(this.GetID(TYPE_USERJOIN));
            pkt.ChannelID = -1;
            pkt.DestinationUserID = -1;
            if (this.currentUser != null)
                pkt.SourceUserID = this.currentUser.UserID;
            pkt.SerializeData(message);
            if (this.client != null)
                this.client.Send(pkt);
        }

        /// <summary>
        /// Gets a reference to the client that initialized this module
        /// </summary>
        public IClient Client
        {
            get
            {
                return this.client;
            }
        }
        /// <summary>
        /// Called from the server when the module is first loaded
        /// </summary>
        /// <param name="component">The <see cref="IServer"/> that loaded the module</param>
        public void Initialize(IServer component)
        {
            this.server = component;
            this.server.UserConnected += new UserEventHandler(server_UserConnected);
            this.server.UserDisconnected += new UserEventHandler(server_UserDisconnected);
            this.server.ServerInfoChanged += new Action(server_ServerInfoChanged);
            this.permsHandler = new ServerPermissionsHandler(this.server);
        }

        /// <summary>
        /// Generates a server information message and puts it in a new packet which is returned
        /// </summary>
        /// <returns></returns>
        private Packet GenerateServerInfo()
        {
            if (this.server != null)
            {
                Packet ret = new Packet(this.GetID(TYPE_SERVERINFO));
                ret.DestinationUserID = -1;
                ret.SourceUserID = -1;
                ret.ChannelID = -1;
                ret.SerializeData(new ServerInfoMessage { Name = this.server.Name, MotD = this.server.MotD });
                return ret;
            }
            else
                return null;
        }

        /// <summary>
        /// Broadcasts new server informations to all users
        /// </summary>
        void server_ServerInfoChanged()
        {
            Packet infoMsg = this.GenerateServerInfo();
            if (infoMsg != null && this.server != null)
                this.Send(infoMsg);
        }

        /// <summary>
        /// Used to broadcast a <see cref="UserDisconnectedMessage"/> for the specified user
        /// </summary>
        /// <param name="user">The user that disconnected</param>
        void server_UserDisconnected(IUser user)
        {
            if (this.server != null)
            {
                Packet pkt = new Packet(this.GetID(TYPE_USERDC));
                pkt.ChannelID = -1;
                pkt.DestinationUserID = -1;
                pkt.SourceUserID = -1;
                pkt.SerializeData(new UserDisconnectedMessage { UserID = user.UserID });
                this.server.Broadcast(pkt);
            }
        }

        /// <summary>
        /// Used to send an instance of <see cref="RemoteUser"/> to the newly connected user
        /// <para/>
        /// Also sends server informations
        /// </summary>
        /// <param name="user">The user that has connected</param>
        void server_UserConnected(IUser user)
        {
            if (this.server != null)
            {
                RemoteUser rUser = new RemoteUser(user);
                Packet pkt = new Packet(this.GetID(TYPE_USERINFO));
                pkt.SourceUserID = user.UserID;
                pkt.DestinationUserID = user.UserID;
                pkt.ChannelID = user.UserID;
                pkt.SerializeData(rUser);
                user.Send(pkt);
                user.Send(this.GenerateServerInfo());
            }
        }

        /// <summary>
        /// Gets a reference to the current user of this module (client side only)
        /// </summary>
        public IUser CurrentUser
        {
            get { return this.currentUser; }
        }

        /// <summary>
        /// Attempts to find the source <see cref="IUser"/> instance that sent the specified packet
        /// </summary>
        /// <param name="userID">the ID of the user to get the <see cref="IUser"/> instance for</param>
        /// <returns>An <see cref="IUser"/> instance that represents the user with the specified UserID if found, otherwise null</returns>
        private IUser GetUser(int userID)
        {
            IUser ret = null;
            if (this.server != null)
                this.server.UserHandler.TryGetUser(userID, out ret);
            else if (this.client != null)
                this.discoveredUsers.TryGetValue(userID, out ret);
            return ret;
        }

        /// <summary>
        /// Attempts to find the user with the specified username
        /// </summary>
        /// <param name="username"></param>
        /// <returns></returns>
        private IUser FindUser(string username)
        {
            IUser ret = null;
            if (this.server != null)
                this.server.UserHandler.TryGetUser(username, out ret);
            return ret;
        }

        /// <summary>
        /// Called from the owning component whenever a packet has been received that is destined for this module
        /// </summary>
        /// <param name="packet">The packet that was received</param>
        public void OnPacket(Packet packet)
        {
            lock (this.p_Lock)
            {
                Action<Packet, IUser> a;
                if (this.actions.TryGetValue(packet.TypeID, out a))
                {
                    IUser source = this.GetUser(packet.SourceUserID);
                    try { a.Invoke(packet, source); }
                    catch (Exception e)
                    {
                        if (this.server != null)
                            this.SendBasicMessage("Segmentation Fault, Core dumped...", source);
                        base.LogException(e, a.Method);
                    }
                }
            }
        }

        /// <summary>
        /// Called client-side when a <see cref="RemoteUser"/> packet has been received
        /// </summary>
        /// <param name="packet">The packet that contains the <see cref="RemoteUser"/></param>
        /// <param name="sourceUser">The user that sent the packet (usually null)</param>
        private void OnUserInfo_Client(Packet packet, IUser sourceUser)
        {
            RemoteUser user = packet.DeserializeData<RemoteUser>();
            if (user != null)
            {
                if (packet.SourceUserID == packet.DestinationUserID)// Indicates that the informations being sent are for the current user
                    this.currentUser = user;

                if (this.discoveredUsers.ContainsKey(user.UserID))
                {
                    IUser old = this.discoveredUsers[user.UserID];
                    this.discoveredUsers[user.UserID] = user;
                    this.OnUserUpdated(old, user);
                }
                else
                    this.discoveredUsers.Add(user.UserID, user);
            }
        }

        /// <summary>
        /// This type is used for UserInfo messages
        /// </summary>
        public const string TYPE_USERINFO = "Core.UserInfo";

        /// <summary>
        /// This type is used for UserJoined messages
        /// </summary>
        public const string TYPE_USERJOIN = "Core.UserJoined";

        /// <summary>
        /// This type is used for UserLeft messages
        /// </summary>
        public const string TYPE_USERLEFT = "Core.UserLeft";

        /// <summary>
        /// This type is used for GetPermissions messages
        /// </summary>
        public const string TYPE_GETPERMS = "Core.GetPermissions";

        /// <summary>
        /// This type is used to <see cref="UserPermissions"/> messages
        /// </summary>
        public const string TYPE_USERPERMS = "Core.UserPermissions";

        /// <summary>
        /// This type is used for the "initial" channel informations sent by the server to the client when the client first joins a channel
        /// </summary>
        public const string TYPE_CINFOMSG = "Core.ChannelInfo";

        /// <summary>
        /// This type is used for basic messages
        /// </summary>
        public const string TYPE_MESSAGE = "Core.BasicMessage";

        /// <summary>
        /// This type is used for <see cref="UserDisconnectedMessage"/>s
        /// </summary>
        public const string TYPE_USERDC = "Core.UserDisconnected";

        /// <summary>
        /// This type is used for <see cref="ServerInfoMessage"/>s
        /// </summary>
        public const string TYPE_SERVERINFO = "Core.ServerInfo";

        /// <summary>
        /// This type is used for <see cref="ChannelList"/> messages
        /// </summary>
        public const string TYPE_CHANNELLIST = "Core.ChannelList";

        /// <summary>
        /// This type is used for <see cref="ChannelControlMessage"/>s
        /// </summary>
        public const string TYPE_CHANNELCTRL = "Core.ChannelControlMessage";

        private static readonly string[] types = new string[]
        {
            TYPE_USERINFO,
            TYPE_USERJOIN,
            TYPE_USERLEFT,
            TYPE_GETPERMS,
            TYPE_USERPERMS,
            TYPE_CINFOMSG,
            TYPE_MESSAGE,
            TYPE_SERVERINFO,
            TYPE_CHANNELLIST,
            TYPE_CHANNELCTRL,
            TYPE_USERDC,
        };
        
        /// <summary>
        /// Specifies which types this module can handle
        /// </summary>
        public string[] Types
        {
            get { return types; }
        }

        /// <summary>
        /// Client-Side: Sends the specified packet using the <see cref="IClient"/> that loaded this module
        /// <para/>
        /// Server-Side: Broadcasts the specified packet to ALL connected users
        /// </summary>
        /// <param name="packet">The packet to send</param>
        public void Send(Packet packet)
        {
            if (packet != null)
            {
                if (this.client != null)
                {
                    if (this.currentUser != null)
                        packet.SourceUserID = this.currentUser.UserID;
                    this.client.Send(packet);
                }
                else if (this.server != null)
                    this.server.Broadcast(packet);
            }
        }

        private volatile int nextID = int.MinValue;

        /// <summary>
        /// Attempts to get the permissions for the specified user on the specified channel from the server within the given timeout period
        /// </summary>
        /// <param name="username">The username of the user to get the permissions for</param>
        /// <param name="channelID">The ID of the channel to get the permissions for</param>
        /// <param name="timeout">The timeout (in milliseconds) of the operation</param>
        /// <returns>A <see cref="UserPermissions"/> object representing the permissions of the specified user on the specified channel, if no answer was received within the given timeout period, a <see cref="TimeoutException"/> is thrown</returns>
        public UserPermissions RequestPermissions(string username, int channelID, int timeout)
        {
            if (this.currentUser != null)
            {
                int id = this.nextID++;
                Packet request = new Packet(this.GetID(TYPE_GETPERMS));
                request.DestinationUserID = -1;
                request.ChannelID = -1;
                request.SourceUserID = this.currentUser.UserID;
                request.SerializeData(new GetPermissionsMessage { RequestID = id, ChannelID = channelID, Username = username });
                this.requestedPermissions.Add(id, null);
                this.currentUser.Send(request);
                DateTime start = DateTime.Now;
                while (this.requestedPermissions[id] == null)
                {
                    System.Threading.Thread.Sleep(10);
                    if ((DateTime.Now - start).TotalMilliseconds >= timeout)
                    {
                        this.requestedPermissions.Remove(id);
                        throw new TimeoutException("The request has timed out");
                    }
                }
                UserPermissions response = this.requestedPermissions[id];
                this.requestedPermissions.Remove(id);
                return response;
            }
            else
                throw new InvalidOperationException("Unable to request permissions without a current user");
        }

        /// <summary>
        /// Called from the owner when an ID has been determined for a specific packet type
        /// </summary>
        /// <param name="type">The type that the ID has been discovered for</param>
        /// <param name="id">The ID...</param>
        public void SetTypeID(string type, uint id)
        {
            if (this.typeMap.ContainsKey(type))
                return;
            else
            {
                this.typeMap.Add(type, id);
                switch (type)
                {
                    case TYPE_USERINFO:
                        if (this.client != null)
                            this.actions.Add(id, new Action<Packet, IUser>(this.OnUserInfo_Client));
                        break;
                    case TYPE_USERJOIN:
                        if (this.client != null)
                            this.actions.Add(id, new Action<Packet, IUser>(this.OnUserJoined_Client));
                        else if (this.server != null)
                            this.actions.Add(id, new Action<Packet, IUser>(this.OnUserJoined_Server));
                        break;
                    case TYPE_USERLEFT:
                        if (this.client != null)
                            this.actions.Add(id, new Action<Packet,IUser>(this.OnUserLeft_Client));
                        else if (this.server != null)
                            this.actions.Add(id, new Action<Packet,IUser>(this.OnUserLeft_Server));
                        break;
                    case TYPE_USERDC:
                        if (this.client != null)
                            this.actions.Add(id, new Action<Packet, IUser>(this.OnUserDisconnected_Client));
                        break;
                    case TYPE_GETPERMS:
                        if (this.server != null)
                            this.actions.Add(id, new Action<Packet, IUser>(this.OnGetPerms_Server));
                        break;
                    case TYPE_USERPERMS:
                        if (this.client != null)
                            this.actions.Add(id, new Action<Packet, IUser>(this.OnUserPermissions_Client));
                        break;
                    case TYPE_CINFOMSG:
                        if (this.client != null)
                            this.actions.Add(id, new Action<Packet, IUser>(this.OnChannelInfo_Client));
                        break;
                    case TYPE_MESSAGE:
                        if (this.client != null)
                            this.actions.Add(id, new Action<Packet, IUser>(this.OnMessage_Client));
                        else if (this.server != null)
                            this.actions.Add(id, new Action<Packet, IUser>(this.OnMessage_Server));
                        break;
                    case TYPE_SERVERINFO:
                        if (this.client != null)
                            this.actions.Add(id, new Action<Packet, IUser>(this.OnServerInfo_Client));
                        break;
                    case TYPE_CHANNELLIST:
                        if (this.client != null)
                            this.actions.Add(id, new Action<Packet, IUser>(this.OnChannelList_Client));
                        else if (this.server != null)
                            this.actions.Add(id, new Action<Packet, IUser>(this.OnChannelList_Server));
                        break;
                    case TYPE_CHANNELCTRL:
                        if (this.server != null)
                            this.actions.Add(id, new Action<Packet, IUser>(this.OnChannelControl_Server));
                        break;
                }
            }
        }

        /// <summary>
        /// Garbage Disposal...
        /// </summary>
        public void Dispose()
        {
            this.actions.Clear();
            this.actions = null;
        }
    }
}
